<?php

namespace SinoPHP\BbdBoss\Library;

use SinoPHP\BbdBoss\Defines\StaticConfig;
use SinoPHP\BbdBoss\Library\Dnspod;

/**
 * IP的监控相关操作封装
 *
 * IP子域库：
 *     如：ip.libary+.com，作为解析到IP的域名
 *     解析线路默认为“默认”线路
 *
 * 记录监控：
 *     给解析到IP的记录添加监控
 *     每个子域一条监控
 *
 * 同线路记录上限：
 *     DNSPOD API 每条下路下A记录上限数量 默认50 不得超过50
 */
class IpMonitor {

    /**
     * 默认TTL
     */
    const DEFAULT_TTL = 10;

    /**
     * DNSPOD 对象
     * 
     * @var null|object
     */
    private $dnspodObj = null;

    /**
     * IP库子域db对象
     * 
     * @var null|object
     */
    private $ipDomainObj = null;

    /**
     * 记录上限
     * 
     * @var integer
     */
    private $recordUpper = 30;

    public function __construct() {
        $this->dnspodObj = new Dnspod("inner");
        $this->ipDomainObj = model("ServerIpDomain");

        // 记录上限
        $this->recordUpper = config("ip", 'dnspod')['upperLimit'];
    }

    /**
     * 添加IP的解析记录
     * 
     * @param  array $ipData IP索引数组
     *                     ['1.1.1.1', '2.2.2.2']
     * @return array $recordData IP 作为数组Key
     *                     recordId:    DNSPOD ID
     *                     ipDomainId:  IP子域库子域ID
     *                     monitorId:   DNSPOD 监控ID
     */
    public function addIpMonitor(array $ipData) {
        // IP解析记录信息
        $recordData = array();

        // 获取组合解析记录参数
        $params = $this->getRecordParamsReady($ipData);
        if (empty($params)) {
            return [
                'error' => 1,
                'msg' => "IP添加监控传递参数有误"
            ];
        }

        // DNSPOD 添加记录、监控
        $recordResult = $this->addDnspodRecord($params);

        if (1 == $recordResult['error']) {
            return  $recordResult;
        }

        // 处理IP库域名信息
        $dbRes = $this->ipDomainDbReact($params);
        
        return $recordResult;
    }

    /**
     * 修改IP监控
     * 
     * @param  array $ipData
     *                 ip
     *                 dnspod_id
     * @return array
     */
    public function editIpMonitor($ipData) {
        if (!is_array(reset($ipData))) {
            $ipData = array($ipData);
        }

        $dnspodData = array();
        foreach($ipData as $ipInfo) {
            $dnspodData[$ipInfo['ip']] = [
                'recordId' => $ipInfo['dnspod_id'],
                // TODO 需要修改的IP
            ];
        }

        if (empty($dnspodData)) {
            return false;
        }

        return $response = $this->dnspodObj->editRecordMulti($dnspodData);
    }

    /**
     * 删除 IP 监控
     * 
     * @param  array $ipData 一维/二维数组
     *                   ip_domain_id
     *                   dnspod_id
     *                   ip
     * @return array $ipResult 成功的IP数组
     */
    public function deleteIpMonitor($ipData) {
        if (empty($ipData)) {
            return false;
        }

        // 兼容单个、批量操作
        if (!is_array(reset($ipData))) {
            $ipData = array($ipData);
        }

        // 删除有监控的解析记录
        $ipResult = $this->deleteDnspodRecord($ipData);

        return $ipResult;
    }

    /**
     * 确认IP子域记录数量 --备用
     * 
     * @param  array  $ipDomainNum
     *                 ip_domain_id
     *                 num
     * @return boolen
     */
    public function confirmIpRecordNum($ipDomainNum) {
        if (empty($ipDomainNum)) {
            return false;
        }

        if(!is_array($ipDomainNum)) {
            $ipDomainNum = array($ipDomainNum);
        }

        $thisData = array();
        foreach ($ipDomainNum as $data) {
            $thisData = [
                'ip_domain_id' => $data['ip_domain_id'],
                'num' => $data['num']
            ];
        }
        $dbRes = model("ServerIpDomain")->insert($thisData, true);

        return true;
    }

    /**
     * 删除有监控的解析记录 DNSPOD
     * 
     * @param  array $ipData
     * @return
     */
    private function deleteDnspodRecord($ipData) {
        if (empty($ipData)) {
            return;
        }

        // 获取IP库子域列表
        $ipDomainIds = array();
        $ipDomainIds = @array_column($ipData, 'ip_domain_id');
        $ipDomainList = $this->getIpDomain('list', ['ip_domain_id' => $ipDomainIds]);
        $betterIpDoaminList = array();
        foreach ($ipDomainList as $domain) {
            $betterIpDoaminList[$domain['ip_domain_id']] = $domain['num'];
        }

        $deleteDnspodData = array();    // DNSPOD 删除记录数据
        $stayIpData = array();
        foreach ($ipData as $key => $data) {
            $deleteDnspodData[$data['ip']] = [
                'recordId' => $data['dnspod_id']
            ];
            $stayIpData[$data['ip']] = $data;
        }
        if (empty($deleteDnspodData)) {
            return;
        }

        // 删除DNSPOD 记录
        $response = $this->dnspodObj->deleteRecordMulti($deleteDnspodData);
        if (empty($response['success'])) {
            // TODO 删除记录失败策略
            return false;
        }

        $ipDomainData = array();    // IP子域记录数量数据
        $deleteIpData = array();
        $success = $response['success'];
        foreach ($success as $ip => $callback) {
            if ($callback['status']['code'] != 1 && $callback['status']['code'] != 8) {
                // 失败策略
                continue;
            }

            // 计算IP子域记录数量
            $thisIpDomainId = $stayIpData[$ip]['ip_domain_id'];
            $thisNum = !empty($ipDomainData[$thisIpDomainId]['num']) ? $ipDomainData[$thisIpDomainId]['num'] : $betterIpDoaminList[$thisIpDomainId];
            $ipDomainData[$thisIpDomainId] = [
                'ip_domain_id' => $thisIpDomainId,
                'num' =>  ($thisNum -1) >= 0 ? ($thisNum -1) : 0
            ];

            // 删除成功的IP数组
            $deleteIpData[$ip] = $ip;
        }

        // 更新数量
        if (!empty($ipDomainData)) {
            $dbRes = model("ServerIpDomain")->insert($ipDomainData, true);
        }

        return $deleteIpData;
    }

    /**
     * IP库域名添加、修改记录
     * 
     * @param  array $params
     *                 ipDomainUpdateData
     *                 ipDomainAddData
     * @return mixed
     */
    private function ipDomainDbReact($params) {
        $ipDomainObj = $this->ipDomainObj;
        $dbRes = null;

        // 更新
        $domainData = array();
        if (!empty($params['ipDomainUpdateData'])) {
            $ipDomainUpdateData = $params['ipDomainUpdateData'];
            foreach ($ipDomainUpdateData as $key => $data) {
                $domainData[$data['ip_domain_id']] = [
                    'ip_domain_id' => $data['ip_domain_id'],
                    // 'label' => $data['label'],
                    'num' => $data['num']
                ]; 
            }

            $dbRes = $ipDomainObj->insert($domainData, true);
        }

        // 添加
        if (!empty($params['ipDomainAddData'])) {
            $ipDomainAddData = $params['ipDomainAddData'];
            $dbRes = $ipDomainObj->insert($ipDomainAddData);
        }

        return $dbRes;
    }

    /**
     * 添加DNSPOD 解析记录
     * 
     * @param  array $params
     * @return array 
     */
    private function addDnspodRecord(array &$params) {
        if (empty($params['dnspodData'])) {
            $params = [];
            return [
                'error' => 1,
                'msg' => 'IP添加监控缺失[dnspodData]参数'
            ];
        }
        $dnspodData = $params['dnspodData'];
        $dnspodObj = $this->dnspodObj;

        // DNSPOD 下发记录
        $response = $dnspodObj->addRecordMulti($dnspodData);

        if (empty($response['success'])) {
            $params = [];
            return [
                'error' => 1,
                'msg' => "IP添加监控，下发解析记录失败"
            ];
        }

        $failNum = 0;
        $success = $response['success'];
        $recordData = $ipDomainData = array();
        $failMsg = null;
        foreach ($success as $key => $callback) {
            // A记录负载均衡超限制
            if (500025 == $callback['status']['code']) {
                $ipDomainData[] = [
                    'ip_domain_id' => $dnspodData[$key]['ipDomainId'],
                    'num' => $this->recordUpper,
                ];
            }

            if ($callback['status']['code'] != 1) {
                $msgTemp = $dnspodObj->getDnspodCodeMsg($callback['status']['code'], 'record');
                $failMsg = "IP[{$dnspodData[$key]['record']}] 添加监控失败 {$response['status']['code']} {$msgTemp};\n";
                $failNum ++;
                continue;
            }
            // 项目IP管理域名记录数据
            $recordData[$dnspodData[$key]['record']] = [
                'recordId' => $callback['record']['id']
            ];
            $recordData[$dnspodData[$key]['record']]['ipDomainId'] = !empty($dnspodData[$key]['ipDomainId']) ? $dnspodData[$key]['ipDomainId'] : 0;

            if (!empty($callback['monitor']['id'])) {
                $recordData[$dnspodData[$key]['record']]['monitorId'] = $callback['monitor']['id'];
            }

            // 准备监控参数
            $params['dnspodDataTemp'][$dnspodData[$key]['record']] = $dnspodData[$key];
        }

        // 子域下数量上限已达到，避免一直无法添加问题
        if (!empty($ipDomainData)) {
            $this->ipDomainDbReact(['ipDomainUpdateData' => $ipDomainData]);
        }

        // 有失败任务，回滚
        if ($failNum > 0) {
            // 回滚
            $this->dnspodRecordRollback($recordData);
            return [
                'error' => 1,
                'msg' => $failMsg
            ];
        }

        // 添加监控
        $monitorResult = $this->addDnspodMonitor($params, $recordData);

        return $monitorResult;
    }

    /**
     * 回滚添加过得记录
     * 
     * @param  array  $recordData
     * @return 
     */
    private function dnspodRecordRollback($recordData) {
        if (empty($recordData)) {
            return;
        }

        if (!is_array(reset($recordData))) {
            $recordData = array($recordData);
        }

        // 删除记录
        $response = $this->dnspodObj->deleteRecordMulti($recordData);
    }

    /**
     * 添加DNSPOD 监控
     * 
     * @param  array  $params
     * @param  array  $recordData
     * @return array 
     */
    private function addDnspodMonitor($params, $recordData) {

        $dnspodData = $params['dnspodDataTemp'];
        $domainSuffix = config("domain", "dnspod")['domainSuffix']['inner'];
        $bakIp = config("api", "dnspod")['bakIp']['pass'];

        $monitorData = array();
        foreach ($recordData as $key => $data) {
            if (!empty($data['monitorId'])) {
                continue;
            }

            $monitorData[$key] = [
                'recordId' => $data['recordId'],
                'recordHost' => $dnspodData[$key]['subDomain'] . $domainSuffix,
                'bakIp' => $bakIp
            ];
        }

        // 添加监控
        $response = $this->dnspodObj->addMonitorMulti($monitorData);
        if (empty($response['success'])) {
            // 回滚
            $this->dnspodRecordRollback($recordData);
            return [
                'error' => 1,
                'msg' => "IP添加监控失败"
            ];
        }

        $success = $response['success'];
        $failMsg = null;
        foreach ($success as $key => $callback) {
            // 是否监控数量超过上限
            $this->monitorWarnMail($callback);

            if ($callback['status']['code'] != 1 && $callback['status']['code'] != 20) {
                // 回滚
                // $this->dnspodRecordRollback($recordData[$key]);
                // unset($recordData[$key]);
                $msgTemp = $this->dnspodObj->getDnspodCodeMsg($callback['status']['code'], 'monitor');
                $failMsg = "IP[{$dnspodData[$key]['record']}] 添加监控失败 {$callback['status']['code']}{$msgTemp}";
                break;
            }

            $recordData[$key]['monitorId'] = $callback['monitor']['monitor_id'];
        }

        // 回滚
        if (!empty($failMsg)) {
            $this->$dnspodRecordRollback($recordData);
            return [
                'error' => 1,
                'msg' => $failMsg,
            ];
        }

        return [
            'error' => 0,
            'data' => $recordData
        ];
    }

    /**
     * 监控警告邮件
     * 
     * @param  array $monitorResponse dnspod 监控响应值
     * @return 
     */
    private function monitorWarnMail($monitorResponse) {
        if (@$monitorResponse['status']['code'] != 21) {
            return;
        }

        if (!config("warnEmail", 'dnspod')) {
            return;
        }
        $mailAddress = config("warnEmail", 'dnspod');
        $mailSubject = 'DNSPOD监控数量超限';
        $content = "IP监控添加，DNSNPOD监控数量超限，请及时处理或购买监控！";
        sendMail($mailSubject, $mailAddress, $content);
    }

    /**
     * 准备IP解析记录参数
     * 
     * @param  array  $ipData IP一维索引数组
     * @return array
     */
    private function getRecordParamsReady(array $ipData) {
        // 记录上限
        $recordUpper = $this->recordUpper;

        if (empty($ipData)) {
            return;
        }
        // 去重、重新索引
        $ipData = array_values(array_unique($ipData));

        // 可用的域名列表
        $availableDomainList = $this->getAvailableIpDomainList($ipData, $recordUpper);
        if (empty($availableDomainList)) {
            return [];
        }

        // 组合解析记录参数
        $dnspodReadyData = array();
        $surplus = 0;
        $initCount = 0;
        $ipDomainUpdateData = array();
        foreach ($availableDomainList as $key => $domainInfo) {
            $surplus += ($recordUpper - $domainInfo['num']);
            if ($surplus <= 0) {
                continue;
            }
            for ($i = $initCount; $i < $surplus; $i++) {
                $dnspodReadyData[$i] = [
                    'subDomain'  => $domainInfo['domain'],
                    'recordType' => "A",
                    'recordLine' => '默认',
                    'ttl'        => self::DEFAULT_TTL,
                    // 组合入库参数用
                    'ipDomainId' => $domainInfo['ip_domain_id']
                ];
            }

            // 更新子域记录数
            $ipDomainUpdateData[$domainInfo['domain']] = [
                'num' => $domainInfo['num'],
                'label' => $domainInfo['label'],
                'ip_domain_id' => $domainInfo['ip_domain_id']
            ];

            $initCount = $surplus;
        }

        $dnspodData = array();
        // 未达上限子域
        foreach ($ipData as $key => $ip) {
            if (empty($dnspodReadyData[$key])) {
                continue;
            }

            $dnspodData[$key] = array_merge($dnspodReadyData[$key], ['record' => $ip]);
            $ipDomainUpdateData[$dnspodReadyData[$key]['subDomain']]['num'] ++;
            unset($ipData[$key]);
        }

        // 新的子域
        if (!empty($ipData)) {
            // $this->error(104007, 'IP库子域不够');
        }

        return [
            'dnspodData' => $dnspodData,
            'ipDomainUpdateData' => $ipDomainUpdateData
        ];
    }

    /**
     * 获取可用IP库域名列表
     * 
     * @param  array   $ipData IP一维索引数组
     * @param  integer $recordUpper 子域解析记录上限
     * @return array
     */
    private function getAvailableIpDomainList(array $ipData, $recordUpper = 50) {
        // IP库基础子域
        $serverIpDomain = config("ip", 'dnspod')['subDomain'];
        $availableDomainList = array();

        // 可用的子域列表
        $availableDomainList = $this->getIpDomain('list', ['num|<' => $recordUpper], ['num' => 'DESC']);
        $needNum = 0;
        if (empty($availableDomainList)) {
            $needNum = count($ipData);
        } else {
            $workSum = array_sum(array_column($availableDomainList, 'num'));
            $needNum = count($ipData) - (count($availableDomainList) * $recordUpper - $workSum);
        }

        // 生成新子域
        if ($needNum <= 0) {
            return $availableDomainList;
        }

        // 子域数量不够，生成新的子域
        $lastLabelDomain = $this->getIpDomain('row', [], ['label' => 'DESC']);
        if (isset($lastLabelDomain['label'])) {
            $hashKey = $lastLabelDomain['label'] + 1;
        } else {
            $hashKey = 0;
        }
        $doaminPart = explode(".", $serverIpDomain);
        for ($i = 0; $i < $needNum; $i++) {
            if ($i % $recordUpper == 0) {
                $hashKey = $hashKey + ($i / $recordUpper);
            }
            $subDomain = $doaminPart[0] . '.' .  $doaminPart[1] . ($hashKey == 0 ? '' : $hashKey) . '.' . $doaminPart[2];
            $ipDomainAddData[$hashKey] = [
                'domain' => $subDomain,
                'label' => $hashKey
            ];
        }
        // 生成新的子域
        $this->ipDomainDbReact(['ipDomainAddData' => $ipDomainAddData]);
        // 重新获取可用的子域列表
        $availableDomainList = $this->getIpDomain('list', ['num|<' => $recordUpper], ['num' => 'DESC']);

        return $availableDomainList;
    }

    /**
     * 获取ip库子域
     * 
     * @param  string $type
     *                 list: 记录列表
     *                 row:  单条记录
     * @param  array  $where
     * @param  array  $orderBy
     * @return array
     */
    private function getIpDomain($type = 'list', $where, $orderBy = null) {
        $cols = [
            'ip_domain_id',
            'domain',
            'label',
            'num'
        ];
        $functionBox = [
            'list' => 'getList',
            'row' => 'getRow'
        ];

        if ('list' == $type) {
            $result = $this->ipDomainObj->getList($cols, $where, $orderBy);
        }
        if ('row' == $type) {
            $result = $this->ipDomainObj->getRow($cols, $where, $orderBy);
        }

        // 没有表，则初始化表
        if (!$result) {
            $this->dbTableInit();
            return [];
        }

        return $result;
    }

    /**
     * 初始化数据库
     * 
     * @return none
     */
    private function dbTableInit() {
        $tables = db()->getTables();
        $sql = null;

        if (in_array('server_ip_domain', $tables)) {
            return;
        }

        // 新建IP库子域表
        $sql .= file_get_contents(getcwd() . "/../resources/dbdata/server_ip_domain.sql");
        if (empty($sql)) {
            return;
        }
        $res = db()->query($sql);
    }

}
